package com.ballmaze.maze;

import java.util.*;

/**
 * 迷宫管理器
 */
public class MazeManager {
    private int width;
    private int height;
    private int playerCount;
    private MazeMap mazeMap;
    private MazeDrawer mazeDrawer;
    private Player[] players;
    private MazeAction.Direction[] bufferedDirections;
    private Float[] bufferedRewards;
    private MazeLoginManager loginManager;
    private MazeStatusController statusController;
    private boolean alive;
    public static final int VISION_RANGE = 5;

    /**
     * 迷宫管理器构造函数
     *
     * @param width       地图的宽度
     * @param height      地图的高度
     * @param playerCount 用户的个数
     */
    public MazeManager(int width, int height, int playerCount) {
        this.width = width;
        this.height = height;
        this.playerCount = playerCount;
        this.loginManager = new MazeLoginManager(playerCount);
        this.mazeDrawer = new MazeDrawer();
        this.statusController = new MazeStatusController();
        this.bufferedRewards = new Float[playerCount];
        init(width, height, playerCount);
    }

    public MazeLoginManager getLoginManager() {
        return loginManager;
    }

    public MazeDrawer getMazeDrawer() {
        return mazeDrawer;
    }

    public MazeStatusController getStatusController() {
        return statusController;
    }

    private void init(int width, int height, int playerCount) {
        players = new Player[playerCount];
        bufferedDirections = new MazeAction.Direction[playerCount];
        mazeMap = new MazeMap(width, height);
        alive = false;
        for (int i = 0; i < playerCount; i++) {
            players[i] = new Player((int) (Math.random() * width), (int) (Math.random() * height), 1, i);
            mazeMap.insertPlayer(players[i]);
        }
    }

    public void executeActions() {
        HashMap<String, ArrayList<MazePath>> pathCollisionCheck = new HashMap<>();
        /*设置的用户的新坐标*/
        for (int i = 0; i < bufferedDirections.length; i++) {
            Player player = players[i];
            if (bufferedDirections[i] == null) {
                continue;
            }
            //如果当前节点没有给出动作 bufferedActions[i] == null
            int[] moveDirection = getMoveDirection(bufferedDirections[i]);
            //用户的移动倾向
            int newX = player.getX() + moveDirection[0];
            int newY = player.getY() + moveDirection[1];
            MazePath path = new MazePath(player, new MazeLocation(player.getY(), player.getX()), new MazeLocation(newY, newX));
            if (mazeMap.checkAvailable(newY, newX)) {
                if (movePlayer(player, newY, newX)) {
                    List<MazePath> pathList = pathCollisionCheck.getOrDefault(path.collisionCode(), new ArrayList<>(2));
                    pathList.add(path);
                }
            } else {
                //走入不合法的位置的时候 我们给予一个较差的reward
                bufferedRewards[player.getId()] = (float) -0.5;
            }
        }
        /*检测物体碰撞*/
        //检测用户和用户之间的碰撞（过程导向）
        Set<Map.Entry<String, ArrayList<MazePath>>> entries = pathCollisionCheck.entrySet();
        for (Map.Entry<String, ArrayList<MazePath>> entry : entries) {
            ArrayList<MazePath> pathList = entry.getValue();
            for (int i = 0; i < pathList.size(); i++) {
                for (int j = 1; j < pathList.size(); j++) {
                    MazePath pathI = pathList.get(i);
                    MazePath pathJ = pathList.get(j);
                    if (pathI.isCollision(pathJ)) {
                        //发生用户之间的碰撞
                        Player playerA = pathI.getPlayer();
                        Player playerB = pathJ.getPlayer();
                        battleAndDie(playerA, playerB);
                    }
                }
            }
        }
        //检测用户和用户的碰撞（结果导向）
        HashMap<String, Player> collisionMap = new HashMap<>();
        for (int i = 0; i < players.length; i++) {
            Player player = players[i];
            if (!player.isAlive()) {
                continue;
            }
            String positionKey = player.getPositionStr();
            if (!collisionMap.containsKey(positionKey)) {
                collisionMap.put(positionKey, player);
            } else {
                Player oldPlayer = collisionMap.get(positionKey);
                battleAndDie(player, oldPlayer);
            }
        }
        //检测用户和食物之间的碰撞
    }

    /**
     * 两个用户进行battle 决出死者
     *
     * @param playerA
     * @param playerB
     */
    private void battleAndDie(Player playerA, Player playerB) {
        Player.BattleResult result = Player.battle(playerA, playerB);
        try {
            Player loser = result.getLoser();
            playerDie(loser);
            Player loser2 = result.getLoser2();
            if (loser2 != null) {
                playerDie(loser2);
            }
            Player winner = result.getWinner();
            if (winner != null) {
                playerWin(winner);
            }
        } catch (MazeException e) {
            e.printStackTrace();
        }
    }

    private void playerWin(Player winner) {
        bufferedRewards[winner.getId()] = (float) 0.5;
        //胜利者获得能力的提升
        winner.setPower(winner.getPower() + 1);
    }

    /**
     * 玩家阵亡
     *
     * @param loser 死亡的玩家
     */
    private void playerDie(Player loser) throws MazeException {
        loser.setAlive(false);
        mazeMap.removePlayer(loser);
        bufferedRewards[loser.getId()] = (float) -1;
    }

    /**
     * 移动玩家
     *
     * @param player 玩家对象实体
     * @param newY   新坐标位置（进行完可行性检查后的）
     * @param newX   新坐标位置（进行完可行性检查后的）
     */
    public boolean movePlayer(Player player, int newY, int newX) {
        try {
            mazeMap.movePlayer(player, player.getY(), player.getX(), newY, newX);
            player.setY(newY);
            player.setX(newX);
            return true;
        } catch (MazeException e) {
            System.out.println(e);
            return false;
        }
    }

    /**
     * 清楚掉所有的这个缓存的动作于奖励
     */
    public void clearActionsAndRewards() {
        Arrays.fill(bufferedDirections, null);
        Arrays.fill(bufferedRewards, null);
    }

    private int[] getMoveDirection(MazeAction.Direction direction) {
        int[] directions = new int[2];
        if (direction == MazeAction.Direction.UP) {
            directions[1] = 1;
        } else if (direction == MazeAction.Direction.DOWN) {
            directions[1] = -1;
        } else if (direction == MazeAction.Direction.LEFT) {
            directions[0] = -1;
        } else if (direction == MazeAction.Direction.RIGHT) {
            directions[0] = 1;
        }
        return directions;
    }

    @Override
    public String toString() {
        return "MazeManager{" +
                "players=" + Arrays.toString(players) +
                '}';
    }

    public void insertAction(MazeAction action) {
        if (players[action.playerId].isAlive()) {
            bufferedDirections[action.playerId] = action.direction;
        }
    }

    public Player getPlayer(Integer userId) {
        return players[userId];
    }

    public MazeLocation[] getObstacles() {
        return mazeMap.getObstacles();
    }

    public float getReward(Integer userId) {
        if (bufferedRewards[userId] == null) {
            return 0;
        } else {
            return bufferedRewards[userId];
        }
    }

    public class MazeStatusController {
        public void activate() {
            alive = true;
        }

        public void deactivate() {
            alive = false;
        }

        public void restart() {
            loginManager.restart();
            init(width, height, playerCount);
        }

        public boolean isAlive() {
            return alive;
        }

    }

    public class MazeDrawer {
        /**
         * 返回每个用户自己可以看到的视野
         *
         * @param userId
         * @return
         */
        public char[][] getGraphData(Integer userId) {
            int baseOffset = VISION_RANGE / 2;
            char[][] data = new char[VISION_RANGE][VISION_RANGE];
            Player player = players[userId];
            int playerX = player.getX();
            int playerY = player.getY();
            for (int i = 0; i < VISION_RANGE; i++) {
                for (int j = 0; j < VISION_RANGE; j++) {
                    int x = playerX - baseOffset + j;
                    int y = playerY - baseOffset + i;
                    data[i][j] = mazeMap.getValue(y, x);
                }
            }
//            return array2String(data);
            return data;
        }

        private String array2String(char[][] data) {
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < data.length; i++) {
                for (int j = 0; j < data[0].length; j++) {
                    stringBuilder.append(data[i][j]);
                }
                stringBuilder.append('\n');
            }
            return stringBuilder.toString();
        }

        public String toGraphData() {
            StringBuilder builder = new StringBuilder();
            char[][] befProcessedMapData = mazeMap.copyMazeMap();
            for (int i = 0; i < players.length; i++) {
                Player player = players[i];
                befProcessedMapData[player.getY()][player.getX()] = MazeMap.TYPE_PLAYER;
            }
            for (int i = 0; i < mazeMap.height; i++) {
                for (int j = 0; j < mazeMap.width; j++) {
                    builder.append(befProcessedMapData[i][j]);
                }
                builder.append('\n');
            }
            return builder.toString();
        }

    }

    public class MazePath {
        Player player;
        MazeLocation start;
        MazeLocation end;

        public MazePath(Player player, MazeLocation start, MazeLocation end) {
            this.player = player;
            this.start = start;
            this.end = end;
        }

        public String collisionCode() {
            StringBuilder sb = new StringBuilder();
            //power 大的在后面
            if (start.getLocationPower() > end.getLocationPower()) {
                addIntoStringBuilderFromLocation(sb, end, start);
            } else {
                addIntoStringBuilderFromLocation(sb, start, end);
            }
            return sb.toString();
        }

        private void addIntoStringBuilderFromLocation(StringBuilder sb, MazeLocation small, MazeLocation big) {
            sb.append(small.getY());
            sb.append(small.getX());
            sb.append(big.getX());
            sb.append(big.getY());
        }

        public Player getPlayer() {
            return player;
        }

        @Override
        public String toString() {
            return "MazePath{" +
                    "start=" + start +
                    ", end=" + end +
                    '}';
        }

        public boolean isCollision(MazePath path) {
            return start.isSame(path.end) && end.isSame(path.start);
        }
    }
}